﻿// Originally contributed by Chinajade.
//
// LICENSE:
// This work is licensed under the
//     Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
// also known as CC-BY-NC-SA.  To view a copy of this license, visit
//      http://creativecommons.org/licenses/by-nc-sa/3.0/
// or send a letter to
//      Creative Commons // 171 Second Street, Suite 300 // San Francisco, California, 94105, USA.

#region Usings
using System;
using System.Collections.Generic;
using System.Linq;

using Bots.Grind;

using CommonBehaviors.Actions;
using Styx;
using Styx.Common.Helpers;
using Styx.CommonBot;
using Styx.CommonBot.POI;
using Styx.CommonBot.Profiles;
using Styx.CommonBot.Routines;
using Styx.Helpers;
using Styx.Pathing;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;

using Action = Styx.TreeSharp.Action;
#endregion


namespace Honorbuddy.QuestBehaviorCore
{

	public partial class UtilityBehaviorPS
	{		/*

		/// <summary>
		/// Unequivocally engages mob in combat.  Does no checking for being untagged, etc.
		/// </summary>
		/// <remarks>24Feb2013-08:11UTC chinajade</remarks>
		public class SpankMob : PrioritySelector
		{
			public SpankMob(ProvideWoWUnitDelegate selectedTargetDelegate,
							ProvideMovementByDelegate movementByDelegate = null)
			{
				Contract.Requires(selectedTargetDelegate != null, context => "selectedTargetDelegate != null");

				MovementByDelegate = movementByDelegate ?? (context => MovementByType.NavigatorPreferred);
				SelectedTargetDelegate = selectedTargetDelegate;

				Children = CreateChildren();
			}


			// BT contruction-time properties...
			private ProvideMovementByDelegate MovementByDelegate { get; set; }
			private ProvideWoWUnitDelegate SelectedTargetDelegate { get; set; }

			// BT visit-time properties...
			private WaitTimer WatchdogTimer_TimeToEngage { get; set; }
			private WoWUnit SelectedTarget { get; set; }

			// Convenience properties...
			private readonly TimeSpan BlacklistForPullTime = TimeSpan.FromSeconds(3 * 60);
			private readonly TimeSpan MinTimeToEngagement = TimeSpan.FromSeconds(3);


			private List<Composite> CreateChildren()
			{
				return new List<Composite>()
				{
					new Decorator(context =>
					{
						if (!LevelBot.BehaviorFlags.HasFlag(BehaviorFlags.Combat))
						{
							QBCLog.DeveloperInfo("LevelBot has Combat behavior turned off--ignoring SpankMob directive.");
							return false;
						}

						var mob = SelectedTargetDelegate(context);
						var isMobChanged = (mob != SelectedTarget);

						SelectedTarget = mob;

						if (!Query.IsViableForFighting(SelectedTarget))
						{
							// If mob was valid, let user know of problem...
							if (Query.IsViable(SelectedTarget))
							{
								QBCLog.DeveloperInfo("Mob {0} is not viable for fighting--ignoring SpankMob directive.",
									SelectedTarget.SafeName);
							}

							WatchdogTimer_TimeToEngage = null;
							return false;
						}

						// If we have mob aggro, cancel the watchdog timer and allow combat to proceed...
						if (SelectedTarget.Aggro || SelectedTarget.PetAggro)
						{
							WatchdogTimer_TimeToEngage = null;
							return true;
						}
						
						// NB: This twisted logic with watchdog timers is to handle bugged mobs...
						// Once we've decided to go after a mob, we must close the distance and start the fight
						// within a reasonable amount of time.  If that doesn't happen, then we blacklist
						// the mob for combat (and move on).

						// If mob changed, any running Watchdog timer is no longer valid...
						if (isMobChanged)
							{ WatchdogTimer_TimeToEngage = null; }

						// Start watchdog timer for engaging mob...
						if (WatchdogTimer_TimeToEngage == null)
						{
							WatchdogTimer_TimeToEngage =
								new WaitTimer(SelectedTarget.Location.MaximumTraversalTime(2.5, MinTimeToEngagement, TimeSpan.FromSeconds(180)));
							WatchdogTimer_TimeToEngage.Reset();
						}

						// If we are unable to engage an unaggro'd mob in a reasonable amount of time,
						// cancel attack, and blacklist mob...
						if (WatchdogTimer_TimeToEngage.IsFinished)
						{
							QBCLog.Warning("Unable to engage {0} in {1}--pull-blacklisted for {2}",
								SelectedTarget.Name,
								Utility.PrettyTime(WatchdogTimer_TimeToEngage.WaitTime),
								Utility.PrettyTime(BlacklistForPullTime));
							SelectedTarget.BlacklistForPulling(BlacklistForPullTime);
							SelectedTarget = null;
							BotPoi.Clear();
							Me.ClearTarget();
							return false;
						}

						return true;
					},
						new PrioritySelector(
							// Mark target and notify Combat Routine of target, if needed...
							new ActionFail(context => { Utility.Target(SelectedTarget, false, PoiType.Kill); }),

							// Move within 'pull distance' of the target...
							// NB: Combat Routines and/or HBcore will 'stall' if the selected target is beyond a certain
							// range.  Thus, we must make certain that the target is within 'pull distance' before we attempt
							// to engage the mob.
							new Decorator(context => SelectedTarget.Distance > Math.Max(CharacterSettings.Instance.PullDistance, SelectedTarget.MyAggroRange),
								new UtilityBehaviorPS.MoveTo(
									context => SelectedTarget.Location,
									context => SelectedTarget.SafeName,
									MovementByDelegate)),

							// TODO: This needs to go, eventually.  Let the Kill POI do its job.
							// The NeedHeal and NeedCombatBuffs are part of legacy custom class support
							// and pair with the Heal and CombatBuff virtual methods.  If a legacy custom class is loaded,
							// HonorBuddy automatically wraps calls to Heal and CustomBuffs it in a Decorator checking those for you.
							// So, no need to duplicate that work here.
							new Decorator(ctx => RoutineManager.Current.HealBehavior != null,
								RoutineManager.Current.HealBehavior),
							new Decorator(ctx => RoutineManager.Current.CombatBuffBehavior != null,
								RoutineManager.Current.CombatBuffBehavior),
							RoutineManager.Current.CombatBehavior,

							// Keep fighting until mob is dead...
							new ActionAlwaysSucceed()
						))
				};
			}
		}


		/// <summary>
		/// Targets and kills any mob targeting Self or Pet.
		/// </summary>
		/// <returns></returns>
		// TODO: Retire this behavior, once we get target filters installed in all the behaviors that use this class.
		public class SpankMobTargetingUs : PrioritySelector
		{
			public SpankMobTargetingUs(ProvideBoolDelegate ignoreMobsInBlackspotsDelegate,
										ProvideDoubleDelegate nonCompeteDistanceDelegate,
										Func<object, IEnumerable<WoWUnit>> excludedUnitsDelegate = null)
			{
				Contract.Requires(ignoreMobsInBlackspotsDelegate != null, context => "ignoreMobsInBlackspotsDelegate != null");
				Contract.Requires(nonCompeteDistanceDelegate != null, context => "nonCompeteDistanceDelegate != null");

				ExcludedUnitsDelegate = excludedUnitsDelegate ?? (context => Enumerable.Empty<WoWUnit>());
				IgnoreMobsInBlackspotsDelegate = ignoreMobsInBlackspotsDelegate;
				NonCompeteDistanceDelegate = nonCompeteDistanceDelegate;

				Children = CreateChildren();
			}


			// BT contruction-time properties...
			Func<object, IEnumerable<WoWUnit>> ExcludedUnitsDelegate { get; set; }
			private ProvideBoolDelegate IgnoreMobsInBlackspotsDelegate { get; set; }
			private ProvideDoubleDelegate NonCompeteDistanceDelegate { get; set; }

			// BT visit-time properties...
			private WoWUnit SelectedTarget { get; set; }

			// Convenience properties...
			
			
			private List<Composite> CreateChildren()
			{
				return new List<Composite>()
				{
					new SpankMob(MobTargetingUs)
				};
			}


			private bool IsInterestingToUs(WoWUnit wowUnit, bool ignoreMobsInBlackspots, double nonCompeteDistance, IEnumerable<WoWUnit> excludedUnits)
			{
				return
					Query.IsViableForPulling(wowUnit, ignoreMobsInBlackspots, nonCompeteDistance)
					&& (wowUnit.IsTargetingMeOrPet
						|| wowUnit.IsTargetingAnyMinion
						|| wowUnit.IsTargetingMyPartyMember)
					// exclude opposing faction: both players and their pets show up as "PlayerControlled"
					&& !wowUnit.PlayerControlled
					// Do not pull mobs on the AvoidMobs list
					&& !ProfileManager.CurrentOuterProfile.AvoidMobs.Contains(wowUnit.Entry)
					// exclude any units that are candidates for interacting
					&& !excludedUnits.Contains(wowUnit);
			}


			private WoWUnit MobTargetingUs(object context)
			{
				var ignoreMobsInBlackspots = IgnoreMobsInBlackspotsDelegate(context);
				var nonCompeteDistance = NonCompeteDistanceDelegate(context);

				// If a mob is targeting us, deal with it immediately, so subsequent activities won't be interrupted...
				// NB: This can happen if we 'drag mobs' behind us on the way to our destination.
				if (!Query.IsViableForPulling(SelectedTarget, ignoreMobsInBlackspots, nonCompeteDistance))
				{
					using (StyxWoW.Memory.AcquireFrame())
					{
						var excludedUnits = ExcludedUnitsDelegate(context).ToList();

						SelectedTarget =
							(from wowUnit in ObjectManager.GetObjectsOfType<WoWUnit>(true, false)
							 where IsInterestingToUs(wowUnit, ignoreMobsInBlackspots, nonCompeteDistance, excludedUnits)
							 orderby wowUnit.PathTraversalCost()
							 select wowUnit)
							.FirstOrDefault();
					}

					if (Query.IsViable(SelectedTarget))
					{
						Navigator.PlayerMover.MoveStop();
						Utility.Target(SelectedTarget, true, PoiType.Kill);
						TreeRoot.StatusText = string.Format("Spanking {0} that has targeted us.",
							SelectedTarget.Name);
					}
				}

				return SelectedTarget;
			}
		}


		// 24Feb2013-08:11UTC chinajade
		// TODO: Retire this behavior, once we get target filters installed in all the behaviors that use this class.
		public class SpankMobWithinAggroRange : PrioritySelector
		{
			public SpankMobWithinAggroRange(ProvideWoWPointDelegate destinationDelegate,
											ProvideBoolDelegate ignoreMobsInBlackspotsDelegate,
											ProvideDoubleDelegate nonCompeteDistanceDelegate,
											ProvideDoubleDelegate extraRangePaddingDelegate = null,
											Func<object, IEnumerable<int>> excludedUnitIdsDelegate = null)
			{
				Contract.Requires(destinationDelegate != null, context => "destinationDelegate != null");
				Contract.Requires(ignoreMobsInBlackspotsDelegate != null, context => "ignoreMobsInBlackspotsDelegate != null");
				Contract.Requires(nonCompeteDistanceDelegate != null, context => "nonCompeteDistanceDelegate != null");

				DestinationDelegate = destinationDelegate;
				ExtraRangePaddingDelegate = extraRangePaddingDelegate ?? (context => 0.0);
				ExcludedUnitIdsDelegate = excludedUnitIdsDelegate ?? (context => Enumerable.Empty<int>());
				IgnoreMobsInBlackspotsDelegate = ignoreMobsInBlackspotsDelegate;
				NonCompeteDistanceDelegate = nonCompeteDistanceDelegate;

				Children = CreateChildren();
			}


			// BT contruction-time properties...
			private ProvideWoWPointDelegate DestinationDelegate { get; set; }
			private Func<object, IEnumerable<int>> ExcludedUnitIdsDelegate { get; set; }
			private ProvideDoubleDelegate ExtraRangePaddingDelegate { get; set; }
			private ProvideBoolDelegate IgnoreMobsInBlackspotsDelegate { get; set; }
			private ProvideDoubleDelegate NonCompeteDistanceDelegate { get; set; }

			// BT visit-time properties...
			private WoWUnit SelectedTarget { get; set; }

			// Convenience properties...


			private List<Composite> CreateChildren()
			{
				return new List<Composite>()
				{
					new SpankMob(MobWithinAggroRange)
				};
			}


			private bool IsInterestingToUs(WoWUnit wowUnit, bool ignoreMobsInBlackspots, double nonCompeteDistance, IEnumerable<int> excludedUnitIds)
			{
				return
					Query.IsViableForPulling(wowUnit, ignoreMobsInBlackspots, nonCompeteDistance)
					&& wowUnit.IsHostile
					&& wowUnit.IsUntagged()
					// exclude opposing faction: both players and their pets show up as "PlayerControlled"
					&& !wowUnit.PlayerControlled
					// exclude any units that are candidates for interacting
					&& !excludedUnitIds.Contains((int)wowUnit.Entry)
					// Do not pull mobs on the AvoidMobs list
					&& !ProfileManager.CurrentOuterProfile.AvoidMobs.Contains(wowUnit.Entry)
					&& (wowUnit.Location.PathTraversalCost(DestinationDelegate(wowUnit)) <= (wowUnit.MyAggroRange + ExtraRangePaddingDelegate(wowUnit)));
			}


			private WoWUnit MobWithinAggroRange(object context)
			{
				var ignoreMobsInBlackspots = IgnoreMobsInBlackspotsDelegate(context);
				var nonCompeteDistance = NonCompeteDistanceDelegate(context);

				if (!Query.IsViableForPulling(SelectedTarget, ignoreMobsInBlackspots, nonCompeteDistance))
				{
					var excludedUnitIds = ExcludedUnitIdsDelegate(context);

					// If a mob is within aggro range of our destination, deal with it immediately...
					// Otherwise, it will interrupt our attempt to interact or use items.
					using (StyxWoW.Memory.AcquireFrame())
					{
						SelectedTarget =
							(from wowUnit in ObjectManager.GetObjectsOfType<WoWUnit>(true, false)
							 where IsInterestingToUs(wowUnit, ignoreMobsInBlackspots, nonCompeteDistance, excludedUnitIds)
							 orderby wowUnit.PathTraversalCost()
							 select wowUnit)
							.FirstOrDefault();
					}

					if (Query.IsViable(SelectedTarget))
					{
						Utility.Target(SelectedTarget, false, PoiType.Kill);
						TreeRoot.StatusText = string.Format("Spanking {0}({1}) within aggro range ({2:F1}) of our destination.",
							SelectedTarget.Name,
							SelectedTarget.Entry,
							(SelectedTarget.MyAggroRange + ExtraRangePaddingDelegate(context)));
					}
				}

				return SelectedTarget;
			}
		}
			 */
	}

}